<!DOCTYPE html>
<html lang="zh_cn">
<head>
          <title>来玩魔王的咚</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta charset="utf-8" />
        <!-- twitter card metadata -->
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="/images/mowang.png">
<meta name="twitter:site" content="">
<meta name="twitter:title" content="pytest（四）模拟">
<meta name="twitter:description" content="<p>使用pytest的monkeypatch/mock模拟。</p>">
        <!-- OG Tags -->
<meta property="og:url" content="/pytest-monkeypatch.html"/>
<meta property="og:title" content="来玩魔王的咚 | pytest（四）模拟" />
<meta property="og:description" content="<p>使用pytest的monkeypatch/mock模拟。</p>" />
        <!-- favicon -->
        <link rel="icon" type="image/png" href="/images/mowang.png">
        <!-- moment.js for date formatting -->
        <script src="/theme/js/moment.js"></script>
        <!-- css -->
        <link rel="stylesheet" type="text/css" href="/theme/css/main.css" />
        <!-- 左边的menu，如果页面高度不够，就跟着滚动，否则文章分类显示不全 -->
        <link rel="stylesheet" type="text/css" href="/theme/css/mycss/menu.css" />
		<script>
			
                /*! grunt-grunticon Stylesheet Loader - v2.1.2 | https://github.com/filamentgroup/grunticon | (c) 2015 Scott Jehl, Filament Group, Inc. | MIT license. */
    
    (function(e){function t(t,n,r,o){"use strict";function a(){for(var e,n=0;u.length>n;n++)u[n].href&&u[n].href.indexOf(t)>-1&&(e=!0);e?i.media=r||"all":setTimeout(a)}var i=e.document.createElement("link"),l=n||e.document.getElementsByTagName("script")[0],u=e.document.styleSheets;return i.rel="stylesheet",i.href=t,i.media="only x",i.onload=o||null,l.parentNode.insertBefore(i,l),a(),i}var n=function(r,o){"use strict";if(r&&3===r.length){var a=e.navigator,i=e.Image,l=!(!document.createElementNS||!document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect||!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image","1.1")||e.opera&&-1===a.userAgent.indexOf("Chrome")||-1!==a.userAgent.indexOf("Series40")),u=new i;u.onerror=function(){n.method="png",n.href=r[2],t(r[2])},u.onload=function(){var e=1===u.width&&1===u.height,a=r[e&&l?0:e?1:2];n.method=e&&l?"svg":e?"datapng":"png",n.href=a,t(a,null,null,o)},u.src="",document.documentElement.className+=" grunticon"}};n.loadCSS=t,e.grunticon=n})(this);(function(e,t){"use strict";var n=t.document,r="grunticon:",o=function(e){if(n.attachEvent?"complete"===n.readyState:"loading"!==n.readyState)e();else{var t=!1;n.addEventListener("readystatechange",function(){t||(t=!0,e())},!1)}},a=function(e){return t.document.querySelector('link[href$="'+e+'"]')},c=function(e){var t,n,o,a,c,i,u={};if(t=e.sheet,!t)return u;n=t.cssRules?t.cssRules:t.rules;for(var l=0;n.length>l;l++)o=n[l].cssText,a=r+n[l].selectorText,c=o.split(");")[0].match(/US\-ASCII\,([^"']+)/),c&&c[1]&&(i=decodeURIComponent(c[1]),u[a]=i);return u},i=function(e){var t,o,a;o="data-grunticon-embed";for(var c in e)if(a=c.slice(r.length),t=n.querySelectorAll(a+"["+o+"]"),t.length)for(var i=0;t.length>i;i++)t[i].innerHTML=e[c],t[i].style.backgroundImage="none",t[i].removeAttribute(o);return t},u=function(t){"svg"===e.method&&o(function(){i(c(a(e.href))),"function"==typeof t&&t()})};e.embedIcons=i,e.getCSS=a,e.getIcons=c,e.ready=o,e.svgLoadedCallback=u,e.embedSVG=u})(grunticon,this);
                
                grunticon(["/theme/css/icons.data.svg.css", "/theme/css/icons.data.png.css", "/theme/css/icons.fallback.css"]);
            </script>
        <noscript><link href="/theme/css/icons.fallback.css" rel="stylesheet"></noscript>
        <!-- menu toggle javascript -->
        <script type="text/javascript">
            document.addEventListener("DOMContentLoaded", initMenu);
            
            function initMenu(){
                var menu = document.getElementById("menu");
                var menulink = document.getElementById("menu-link");
                menulink.addEventListener("click", function toggleMenu(){
                        window.event.preventDefault();
                        menulink.classList.toggle('active');
                        menu.classList.toggle('active');              
                    });
            };
        </script>
        <!-- 不蒜子 -->
        <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

    <meta name="description" content="<p>使用pytest的monkeypatch/mock模拟。</p>" />

    <meta name="tags" content="测试" />
  <!-- 替换部分base的样式，看文章时，要再宽一点，右边有很多空间可以撑开 -->
  <link rel="stylesheet" type="text/css" href="/theme/css/mycss/article.css" />

</head>
<body>
    <div role="banner" id="masthead">
        <header>
            <a href="/"><img src="/images/mowang.png" alt="McManus Logo"></a>
                <h1>来玩魔王的咚@骑士救兵</h1>
            <a href="#menu" id="menu-link">more stuff</a>
            <nav id="menu">
                <ul>
                        <li><a href="/tags">tags</a></li>
                            <li><a href="/category/cloud.html">Cloud</a></li>
                            <li><a href="/category/docker.html">Docker</a></li>
                            <li><a href="/category/go.html">Go</a></li>
                            <li><a href="/category/linux.html">Linux</a></li>
                            <li class="active"><a href="/category/python.html">Python</a></li>
                            <li><a href="/category/xue-xi-bi-ji.html">学习笔记</a></li>
                            <li><a href="/category/yun-wei-zi-dong-hua.html">运维自动化</a></li>
                </ul>
            </nav>
        </header>
    </div>
        <div class="page" role="main">
  <div class="article" role="article">
    <article>
        <footer>
            <a name="top"></a>
            <p>
              <time datetime=" 2022-03-22 14:10:00+08:00">
                <script>document.write(moment('2022-03-22 14:10:00+08:00').format('LL'));</script>
              </time>
              ~
              <time datetime=" 2022-03-22 14:10:00+08:00">
                <script>document.write(moment('2022-03-24 11:56:00+08:00').format('LL'));</script>
              </time>
            </p>
        </footer>
        <header>
          <h2>
            pytest（四）模拟
          </h2>
        </header>
      <div class="content">
         <div class="toc">
<ul>
<li><a href="#hou-zi-bu-ding">猴子补丁</a></li>
<li><a href="#monkeypatch-gu-jian">monkeypatch固件</a></li>
<li><a href="#mo-ni-han-shu">模拟函数</a><ul>
<li><a href="#yong-gu-jian-feng-zhuang-mo-ni">用固件封装模拟</a></li>
</ul>
</li>
<li><a href="#mo-ni-dui-xiang-shi-li">模拟对象实例</a></li>
<li><a href="#quan-ju-jin-yong-requests-qing-qiu-de-shi-li">全局禁用requests请求的示例</a></li>
<li><a href="#bu-jian-yi-mo-ni-nei-zhi-han-shu">不建议模拟内置函数</a></li>
<li><a href="#yong-with-shang-xia-wen-guan-li-qi-xian-zhi-mo-ni-fan-wei">用with上下文管理器限制模拟范围</a></li>
<li><a href="#xiu-gai-huan-jing-bian-liang">修改环境变量</a></li>
<li><a href="#xiu-gai-zi-dian">修改字典</a></li>
</ul>
</div>
<p>有时候，测试需要依赖全局设置的功能，或者是一些不易测试的代码，比如网络访问。这时候就需要使用模拟（mock），也叫猴子补丁（Monkey Patch）。<br>
在pytest中使用monkeypatch固件，可以在测试中设置（或删除）属性（替换属性也包括替换掉模块或类中的函数或方法）、字典键值对或者是环境变量。  </p>
<h3 id="hou-zi-bu-ding"><a class="toclink" href="#hou-zi-bu-ding">猴子补丁</a></h3>
<p>属性在运行时的动态替换，叫做猴子补丁（Monkey Patch）。  </p>
<p>运行时动态替换方法。比如原本项目中使用json模块，现在有一个ujson模块更好。要使用ujosn模块。可以去把每个加载json模块的语句<code>import json</code>替换成<code>import ujson as json</code>。但是这样做的成本也很高。只需要在入口加上如下的代码就能完成替换：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">ujson</span>

<span class="k">def</span> <span class="nf">monkey_patch_json</span><span class="p">():</span>
    <span class="n">json</span><span class="o">.</span><span class="vm">__name__</span> <span class="o">=</span> <span class="s1">&#39;ujson&#39;</span>
    <span class="n">json</span><span class="o">.</span><span class="n">dumps</span> <span class="o">=</span> <span class="n">ujson</span><span class="o">.</span><span class="n">dumps</span>
    <span class="n">json</span><span class="o">.</span><span class="n">loads</span> <span class="o">=</span> <span class="n">ujson</span><span class="o">.</span><span class="n">loads</span>

<span class="n">monkey_patch_json</span><span class="p">()</span>
</code></pre></div>

<p>运行时动态增加方法。这种场景比较多，比如在使用别人的库时，想要丰富功能，也可以考虑用猴子补丁。不过猴子补丁虽然便利，但是也会把代码搞乱。这种时候还有一个选择就是用继承。  </p>
<h3 id="monkeypatch-gu-jian"><a class="toclink" href="#monkeypatch-gu-jian">monkeypatch固件</a></h3>
<p>monkeypatch固件提供以下这些方法：</p>
<div class="highlight"><pre><span></span><code><span class="n">monkeypatch</span><span class="o">.</span><span class="n">setattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">raising</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">monkeypatch</span><span class="o">.</span><span class="n">setattr</span><span class="p">(</span><span class="s2">&quot;somemodule.obj.name&quot;</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">raising</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">monkeypatch</span><span class="o">.</span><span class="n">delattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">raising</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">monkeypatch</span><span class="o">.</span><span class="n">setitem</span><span class="p">(</span><span class="n">mapping</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
<span class="n">monkeypatch</span><span class="o">.</span><span class="n">delitem</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">raising</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">monkeypatch</span><span class="o">.</span><span class="n">setenv</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">prepend</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
<span class="n">monkeypatch</span><span class="o">.</span><span class="n">delenv</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">raising</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">monkeypatch</span><span class="o">.</span><span class="n">syspath_prepend</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
<span class="n">monkeypatch</span><span class="o">.</span><span class="n">chdir</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
</code></pre></div>

<p><code>raising=True</code>，默认就是True。如果在设置（set）或删除（del）的目标不存在的时候，是否要抛出KeyError或AttributeError异常。<br>
<code>monkeypatch.setattr</code>、<code>monkeypatch.delattr</code>，函数或属性的设置和删除。<br>
<code>monkeypatch.setitem</code>、<code>monkeypatch.delitem</code>，字典修改或删除键值对。<br>
<code>monkeypatch.setenv</code>、<code>monkeypatch.delenv</code>，环境变量设置。<br>
<code>monkeypatch.chdir</code>，切换测试时使用的工作目录。<br>
<code>monkeypatch.syspath_prepend</code>，修改python的sys.path。  </p>
<h3 id="mo-ni-han-shu"><a class="toclink" href="#mo-ni-han-shu">模拟函数</a></h3>
<p>在测试函数的内部再定义一个模拟函数。然后用这个函数去替换掉原本的函数：</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>

<span class="c1"># 需要被模拟的函数，需要替换掉Path.home</span>
<span class="k">def</span> <span class="nf">getssh</span><span class="p">():</span>
    <span class="k">return</span> <span class="n">Path</span><span class="o">.</span><span class="n">home</span><span class="p">()</span> <span class="o">/</span> <span class="s2">&quot;.ssh&quot;</span>

<span class="k">def</span> <span class="nf">test_getssh</span><span class="p">(</span><span class="n">monkeypatch</span><span class="p">):</span>
    <span class="c1"># 准备替换掉Path.home总是返回 &#39;/abc&#39;</span>
    <span class="k">def</span> <span class="nf">mockreturn</span><span class="p">():</span>
        <span class="k">return</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&quot;/abc&quot;</span><span class="p">)</span>

    <span class="c1"># 设置Path的home属性，替换为上面的mockreturn函数</span>
    <span class="n">monkeypatch</span><span class="o">.</span><span class="n">setattr</span><span class="p">(</span><span class="n">Path</span><span class="p">,</span> <span class="s2">&quot;home&quot;</span><span class="p">,</span> <span class="n">mockreturn</span><span class="p">)</span>

    <span class="n">x</span> <span class="o">=</span> <span class="n">getssh</span><span class="p">()</span>
    <span class="k">assert</span> <span class="n">x</span> <span class="o">==</span> <span class="n">Path</span><span class="p">(</span><span class="s2">&quot;/abc/.ssh&quot;</span><span class="p">)</span>
</code></pre></div>

<h4 id="yong-gu-jian-feng-zhuang-mo-ni"><a class="toclink" href="#yong-gu-jian-feng-zhuang-mo-ni">用固件封装模拟</a></h4>
<p>将模拟的逻辑单独写到固件中，这样还可以在其他测试中共享：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">pytest</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">mock_response</span><span class="p">(</span><span class="n">monkeypatch</span><span class="p">):</span>
    <span class="n">monkeypatch</span><span class="o">.</span><span class="n">setattr</span><span class="p">(</span><span class="n">requests</span><span class="p">,</span> <span class="s2">&quot;get&quot;</span><span class="p">,</span> <span class="k">lambda</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">MockResponse</span><span class="p">())</span>

<span class="k">def</span> <span class="nf">test_get_json2</span><span class="p">(</span><span class="n">mock_response</span><span class="p">):</span>
    <span class="n">result</span> <span class="o">=</span> <span class="n">get_json</span><span class="p">(</span><span class="s2">&quot;https://fakeurl&quot;</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">result</span><span class="p">[</span><span class="s2">&quot;mock_key&quot;</span><span class="p">]</span> <span class="o">==</span> <span class="s2">&quot;mock_response&quot;</span>
</code></pre></div>

<h3 id="mo-ni-dui-xiang-shi-li"><a class="toclink" href="#mo-ni-dui-xiang-shi-li">模拟对象实例</a></h3>
<p>下面是一个request模块的调用。这里<code>requests.get</code>是一个网络请求，不易测试，需要模拟掉。<code>requests.get</code>返回的是一个对象，之后还要调用返回该对象的json方法。所以要用于提替换<code>requests.get</code>返回的也需要是一个对象，并且该对象也要有json方法：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">requests</span>

<span class="k">def</span> <span class="nf">get_json</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
    <span class="n">r</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">r</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
</code></pre></div>

<p>下面是对模拟和测试的代码，这里定义了一个包含json方法的类，使用这个类的一个实例化对象来模拟<code>requests.get</code>返回的对象：</p>
<div class="highlight"><pre><span></span><code><span class="c1"># 用于替换的类</span>
<span class="k">class</span> <span class="nc">MockResponse</span><span class="p">:</span>

    <span class="nd">@staticmethod</span>
    <span class="k">def</span> <span class="nf">json</span><span class="p">():</span>
        <span class="k">return</span> <span class="p">{</span><span class="s2">&quot;mock_key&quot;</span><span class="p">:</span> <span class="s2">&quot;mock_response&quot;</span><span class="p">}</span>

<span class="k">def</span> <span class="nf">test_get_json</span><span class="p">(</span><span class="n">monkeypatch</span><span class="p">):</span>

    <span class="k">def</span> <span class="nf">mock_get</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">MockResponse</span><span class="p">()</span>

    <span class="n">monkeypatch</span><span class="o">.</span><span class="n">setattr</span><span class="p">(</span><span class="n">requests</span><span class="p">,</span> <span class="s2">&quot;get&quot;</span><span class="p">,</span> <span class="n">mock_get</span><span class="p">)</span>

    <span class="n">result</span> <span class="o">=</span> <span class="n">get_json</span><span class="p">(</span><span class="s2">&quot;https://fakeurl&quot;</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">result</span><span class="p">[</span><span class="s2">&quot;mock_key&quot;</span><span class="p">]</span> <span class="o">==</span> <span class="s2">&quot;mock_response&quot;</span>
</code></pre></div>

<p>封装成固件：</p>
<div class="highlight"><pre><span></span><code><span class="c1"># MockResponse 类的定义同上，就省略了</span>
<span class="kn">import</span> <span class="nn">pytest</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">mock_response</span><span class="p">(</span><span class="n">monkeypatch</span><span class="p">):</span>
    <span class="n">monkeypatch</span><span class="o">.</span><span class="n">setattr</span><span class="p">(</span><span class="n">requests</span><span class="p">,</span> <span class="s2">&quot;get&quot;</span><span class="p">,</span> <span class="k">lambda</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">MockResponse</span><span class="p">())</span>

<span class="k">def</span> <span class="nf">test_get_json2</span><span class="p">(</span><span class="n">mock_response</span><span class="p">):</span>
    <span class="n">result</span> <span class="o">=</span> <span class="n">get_json</span><span class="p">(</span><span class="s2">&quot;https://fakeurl&quot;</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">result</span><span class="p">[</span><span class="s2">&quot;mock_key&quot;</span><span class="p">]</span> <span class="o">==</span> <span class="s2">&quot;mock_response&quot;</span>
</code></pre></div>

<h3 id="quan-ju-jin-yong-requests-qing-qiu-de-shi-li"><a class="toclink" href="#quan-ju-jin-yong-requests-qing-qiu-de-shi-li">全局禁用requests请求的示例</a></h3>
<p>用下面的方法，可以禁止所有测试的。要使得全局自动生效，所以将固件设置为自动固件并且放置在<code>conftest.py</code>文件中：</p>
<div class="highlight"><pre><span></span><code><span class="c1"># contents of conftest.py</span>
<span class="kn">import</span> <span class="nn">pytest</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span><span class="p">(</span><span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">no_requests</span><span class="p">(</span><span class="n">monkeypatch</span><span class="p">):</span>
    <span class="sd">&quot;&quot;&quot;Remove requests.sessions.Session.request for all tests.&quot;&quot;&quot;</span>
    <span class="n">monkeypatch</span><span class="o">.</span><span class="n">delattr</span><span class="p">(</span><span class="s2">&quot;requests.sessions.Session.request&quot;</span><span class="p">)</span>
</code></pre></div>

<h3 id="bu-jian-yi-mo-ni-nei-zhi-han-shu"><a class="toclink" href="#bu-jian-yi-mo-ni-nei-zhi-han-shu">不建议模拟内置函数</a></h3>
<p>不建议模拟内置函数，比如：open、compile等。因为这些内置函数也可能会破坏pytest的内部结构。如果此种情况无法避免，这些命令行参数可能会有帮助:</p>
<ul>
<li><code>--tb=native</code></li>
<li><code>--assert=plain</code></li>
<li><code>--capture=no</code></li>
</ul>
<p>但是也不能保证，总之就是尽量避免。  </p>
<h3 id="yong-with-shang-xia-wen-guan-li-qi-xian-zhi-mo-ni-fan-wei"><a class="toclink" href="#yong-with-shang-xia-wen-guan-li-qi-xian-zhi-mo-ni-fan-wei">用with上下文管理器限制模拟范围</a></h3>
<p>模拟stdlib以及一些被pytest使用的第三方库，可能会破坏pytest本身。这种情况下，建议使用<code>with monkeypatch.context()</code>将模拟的范围限制在最小的代码块中：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">functools</span>

<span class="k">def</span> <span class="nf">test_partial</span><span class="p">(</span><span class="n">monkeypatch</span><span class="p">):</span>
    <span class="k">with</span> <span class="n">monkeypatch</span><span class="o">.</span><span class="n">context</span><span class="p">()</span> <span class="k">as</span> <span class="n">m</span><span class="p">:</span>
        <span class="n">m</span><span class="o">.</span><span class="n">setattr</span><span class="p">(</span><span class="n">functools</span><span class="p">,</span> <span class="s2">&quot;partial&quot;</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
        <span class="k">assert</span> <span class="n">functools</span><span class="o">.</span><span class="n">partial</span> <span class="o">==</span> <span class="mi">3</span>
</code></pre></div>

<h3 id="xiu-gai-huan-jing-bian-liang"><a class="toclink" href="#xiu-gai-huan-jing-bian-liang">修改环境变量</a></h3>
<p>如果测试需要，可以修改或者删除环境变量的设置。<br>
下面是一个环境变量的测试示例：</p>
<div class="highlight"><pre><span></span><code><span class="c1"># 被测试的代码</span>
<span class="kn">import</span> <span class="nn">os</span>

<span class="k">def</span> <span class="nf">get_os_user_lower</span><span class="p">():</span>
    <span class="sd">&quot;&quot;&quot;Simple retrieval function.</span>
<span class="sd">    Returns lowercase USER or raises OSError.&quot;&quot;&quot;</span>
    <span class="n">username</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s2">&quot;USER&quot;</span><span class="p">)</span>

    <span class="k">if</span> <span class="n">username</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
        <span class="k">raise</span> <span class="ne">OSError</span><span class="p">(</span><span class="s2">&quot;USER environment is not set.&quot;</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">username</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>


<span class="c1"># 测试函数</span>
<span class="kn">import</span> <span class="nn">pytest</span>

<span class="k">def</span> <span class="nf">test_upper_to_lower</span><span class="p">(</span><span class="n">monkeypatch</span><span class="p">):</span>
    <span class="sd">&quot;&quot;&quot;Set the USER env var to assert the behavior.&quot;&quot;&quot;</span>
    <span class="n">monkeypatch</span><span class="o">.</span><span class="n">setenv</span><span class="p">(</span><span class="s2">&quot;USER&quot;</span><span class="p">,</span> <span class="s2">&quot;TestingUser&quot;</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">get_os_user_lower</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&quot;testinguser&quot;</span>

<span class="k">def</span> <span class="nf">test_raise_exception</span><span class="p">(</span><span class="n">monkeypatch</span><span class="p">):</span>
    <span class="sd">&quot;&quot;&quot;Remove the USER env var and assert OSError is raised.&quot;&quot;&quot;</span>
    <span class="n">monkeypatch</span><span class="o">.</span><span class="n">delenv</span><span class="p">(</span><span class="s2">&quot;USER&quot;</span><span class="p">,</span> <span class="n">raising</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>

    <span class="k">with</span> <span class="n">pytest</span><span class="o">.</span><span class="n">raises</span><span class="p">(</span><span class="ne">OSError</span><span class="p">):</span>
        <span class="n">_</span> <span class="o">=</span> <span class="n">get_os_user_lower</span><span class="p">()</span>
</code></pre></div>

<p>需要考虑两种情况：</p>
<ol>
<li>环境变量被设置为某个值</li>
<li>环境变量不存在</li>
</ol>
<p>封装成固件：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">pytest</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">mock_env_user</span><span class="p">(</span><span class="n">monkeypatch</span><span class="p">):</span>
    <span class="n">monkeypatch</span><span class="o">.</span><span class="n">setenv</span><span class="p">(</span><span class="s2">&quot;USER&quot;</span><span class="p">,</span> <span class="s2">&quot;TestingUser&quot;</span><span class="p">)</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">mock_env_missing</span><span class="p">(</span><span class="n">monkeypatch</span><span class="p">):</span>
    <span class="n">monkeypatch</span><span class="o">.</span><span class="n">delenv</span><span class="p">(</span><span class="s2">&quot;USER&quot;</span><span class="p">,</span> <span class="n">raising</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">test_upper_to_lower</span><span class="p">(</span><span class="n">mock_env_user</span><span class="p">):</span>
    <span class="k">assert</span> <span class="n">get_os_user_lower</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&quot;testinguser&quot;</span>

<span class="k">def</span> <span class="nf">test_raise_exception</span><span class="p">(</span><span class="n">mock_env_missing</span><span class="p">):</span>
    <span class="k">with</span> <span class="n">pytest</span><span class="o">.</span><span class="n">raises</span><span class="p">(</span><span class="ne">OSError</span><span class="p">):</span>
        <span class="n">_</span> <span class="o">=</span> <span class="n">get_os_user_lower</span><span class="p">()</span>
</code></pre></div>

<h3 id="xiu-gai-zi-dian"><a class="toclink" href="#xiu-gai-zi-dian">修改字典</a></h3>
<p>在测试的过程中临时调整字典的键值对，对于键值对的修改或删除是逐个键值对进行的：</p>
<div class="highlight"><pre><span></span><code><span class="n">DEFAULT_CONFIG</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;user&quot;</span><span class="p">:</span> <span class="s2">&quot;user1&quot;</span><span class="p">,</span> <span class="s2">&quot;database&quot;</span><span class="p">:</span> <span class="s2">&quot;db1&quot;</span><span class="p">}</span>

<span class="k">def</span> <span class="nf">create_connection_string</span><span class="p">(</span><span class="n">config</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
    <span class="n">config</span> <span class="o">=</span> <span class="n">config</span> <span class="ow">or</span> <span class="n">DEFAULT_CONFIG</span>
    <span class="k">return</span> <span class="sa">f</span><span class="s2">&quot;User Id=</span><span class="si">{</span><span class="n">config</span><span class="p">[</span><span class="s1">&#39;user&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">; Location=</span><span class="si">{</span><span class="n">config</span><span class="p">[</span><span class="s1">&#39;database&#39;</span><span class="p">]</span><span class="si">}</span><span class="s2">;&quot;</span>


<span class="k">def</span> <span class="nf">test_connection</span><span class="p">(</span><span class="n">monkeypatch</span><span class="p">):</span>
    <span class="n">monkeypatch</span><span class="o">.</span><span class="n">setitem</span><span class="p">(</span><span class="n">DEFAULT_CONFIG</span><span class="p">,</span> <span class="s2">&quot;user&quot;</span><span class="p">,</span> <span class="s2">&quot;test_user&quot;</span><span class="p">)</span>
    <span class="n">monkeypatch</span><span class="o">.</span><span class="n">setitem</span><span class="p">(</span><span class="n">DEFAULT_CONFIG</span><span class="p">,</span> <span class="s2">&quot;database&quot;</span><span class="p">,</span> <span class="s2">&quot;test_db&quot;</span><span class="p">)</span>
    <span class="n">expected</span> <span class="o">=</span> <span class="s2">&quot;User Id=test_user; Location=test_db;&quot;</span>
    <span class="n">result</span> <span class="o">=</span> <span class="n">create_connection_string</span><span class="p">()</span>
    <span class="k">assert</span> <span class="n">result</span> <span class="o">==</span> <span class="n">expected</span>

<span class="k">def</span> <span class="nf">test_missing_user</span><span class="p">(</span><span class="n">monkeypatch</span><span class="p">):</span>
    <span class="n">monkeypatch</span><span class="o">.</span><span class="n">delitem</span><span class="p">(</span><span class="n">DEFAULT_CONFIG</span><span class="p">,</span> <span class="s2">&quot;user&quot;</span><span class="p">,</span> <span class="n">raising</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
    <span class="k">with</span> <span class="n">pytest</span><span class="o">.</span><span class="n">raises</span><span class="p">(</span><span class="ne">KeyError</span><span class="p">):</span>
        <span class="n">_</span> <span class="o">=</span> <span class="n">create_connection_string</span><span class="p">()</span>
</code></pre></div>

<p>封装成固件：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">pytest</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">mock_test_user</span><span class="p">(</span><span class="n">monkeypatch</span><span class="p">):</span>
    <span class="n">monkeypatch</span><span class="o">.</span><span class="n">setitem</span><span class="p">(</span><span class="n">DEFAULT_CONFIG</span><span class="p">,</span> <span class="s2">&quot;user&quot;</span><span class="p">,</span> <span class="s2">&quot;test_user&quot;</span><span class="p">)</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">mock_test_database</span><span class="p">(</span><span class="n">monkeypatch</span><span class="p">):</span>
    <span class="n">monkeypatch</span><span class="o">.</span><span class="n">setitem</span><span class="p">(</span><span class="n">DEFAULT_CONFIG</span><span class="p">,</span> <span class="s2">&quot;database&quot;</span><span class="p">,</span> <span class="s2">&quot;test_db&quot;</span><span class="p">)</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">mock_missing_default_user</span><span class="p">(</span><span class="n">monkeypatch</span><span class="p">):</span>
    <span class="n">monkeypatch</span><span class="o">.</span><span class="n">delitem</span><span class="p">(</span><span class="n">DEFAULT_CONFIG</span><span class="p">,</span> <span class="s2">&quot;user&quot;</span><span class="p">,</span> <span class="n">raising</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">test_connection</span><span class="p">(</span><span class="n">mock_test_user</span><span class="p">,</span> <span class="n">mock_test_database</span><span class="p">):</span>
    <span class="n">expected</span> <span class="o">=</span> <span class="s2">&quot;User Id=test_user; Location=test_db;&quot;</span>
    <span class="n">result</span> <span class="o">=</span> <span class="n">create_connection_string</span><span class="p">()</span>
    <span class="k">assert</span> <span class="n">result</span> <span class="o">==</span> <span class="n">expected</span>

<span class="k">def</span> <span class="nf">test_missing_user</span><span class="p">(</span><span class="n">mock_missing_default_user</span><span class="p">):</span>
    <span class="k">with</span> <span class="n">pytest</span><span class="o">.</span><span class="n">raises</span><span class="p">(</span><span class="ne">KeyError</span><span class="p">):</span>
        <span class="n">_</span> <span class="o">=</span> <span class="n">create_connection_string</span><span class="p">()</span>
</code></pre></div>
      </div>
      <div class="back-to-top">
        <a href="/">HOME</a>
        <a href="#top">TOP</a>
      </div>
    </article>
  </div>
<!-- end article -->
<!-- 页面往下滚动一段之后才会显示TOC -->
<script>
  window.onscroll = function() {
    var tocbox = document.getElementsByClassName('toc')[0];
    var osTop = document.documentElement.scrollTop || document.body.scrollTop;
    var osWidth = document.documentElement.scrollWidth || document.body.scrollWidth;
    // console.log(osTop)
    if (osTop>300 && osWidth>865) {
      tocbox.style.display = "block"
    }
    if (osTop<300 || osWidth<865) {
      tocbox.style.display = "none"
    }
  }
</script>
                <footer>
                    <div class="icons">
                    </div>
                    <span id="busuanzi_container_page_pv" style="padding: 10px">本文阅读量<span id="busuanzi_value_page_pv"></span>次</span>
                    <span id="busuanzi_container_site_pv" style="padding: 10px">本站总访问量<span id="busuanzi_value_site_pv"></span>次</span>
                    <span id="busuanzi_container_site_uv" style="padding: 10px">本站总访客数<span id="busuanzi_value_site_uv"></span>人</span>
                    <p>© <script>document.write(moment().format('YYYY'));</script> 749B</p>
                </footer>
        </div>
</body>
</html>